/*The MIT License

Copyright (c) <1/27/2011> <erick.difiore>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/


package com.d4a5.mapping.core
{
	import flash.display.Graphics;
	import flash.geom.Matrix;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	public class AmfObject
	{
		private static var _idNum:uint = 1;
		
		
		public static const dimensions2:int = 2;
		public static const dimensions3:int = 3;
		public static const dimensions4:int = 4;
		
		public static const vertexDataNone:int = 0;
		public static const vertexDataNumber:int = 1;
		public static const vertexDataObject:int = 2;
		
		public static const typePoint:int = 1;
		public static const typePolyline:int = 3;
		public static const typePolygon:int = 5;
		public static const typeMultipoint:int = 8;
		
		public var attributes:Object;
		
		protected var _geometry:ByteArray;
		protected var _geometryType:int;
		protected var _bounds:Bounds;
		protected var _dimensions:int;
		
		protected var _vertexData:ByteArray;
		protected var _vertexDataType:int;
		
		protected var _storedAsFloat:Boolean = false;
		
		protected var _ringsCount:int = -1;
		protected var _idNum:uint;
		
		
		public function AmfObject( geom:ByteArray , geomType:int , bounds:Bounds , ringCount:int , dimensions:int = 2 , vertexData:ByteArray = null , vertexDataType:int = 0 , attributes:Object = null , storedAsFloat:Boolean = false , idNum:uint = uint.MAX_VALUE )
		{
			this._geometry = geom;
			this._geometryType = geomType;
			this._bounds = bounds;
			this._dimensions = dimensions;
			
			this._vertexData = vertexData;
			this._vertexDataType = vertexDataType;
			this.attributes = (attributes) ? attributes : new Object();
			this._storedAsFloat = storedAsFloat;
			
			this._ringsCount = ringCount;
			
			if( idNum == int.MAX_VALUE ){
				this._idNum = AmfObject._idNum++;
			}
			else{
				this._idNum = idNum;
				AmfObject._idNum = idNum+1;
			}
		}
		
		public function get geometry() : ByteArray {
			return this._geometry;
		}
		public function set geometry( bytes:ByteArray ) : void {}
		
		public function get geometryType() : int {
			return this._geometryType;
		}
		public function set geometryType( type:int ) : void {}
		
		public function get bounds() : Bounds {
			return this._bounds;
		}
		public function set bounds( b:Bounds ) : void {}
		
		public function get dimensions() : int {
			return this._dimensions;
		}
		public function set dimensions( type:int ) : void {}
		
		
		public function get vertexData() : ByteArray {
			return this._vertexData;
		}
		public function set vertexData( bytes:ByteArray ) : void {}
		
		public function get vertexDataType() : int {
			return this._vertexDataType;
		}
		public function set vertexDataType( type:int ) : void {}
		
		
		
		public function get storedAsFloat() : Boolean {
			return this._storedAsFloat;
		}
		public function set storedAsFloat( state:Boolean ) : void {}
		
		
		public function get idNum() : int {
			return this._idNum;
		}
		public function set idNum( type:int ) : void {}
		
		public function get ringsCount() : int {
			return this._ringsCount;
		}
		public function set ringsCount( type:int ) : void {}
		
		
		public function getReadFunction() : Function {
			return ( this._storedAsFloat ) ? this._geometry.readFloat : this._geometry.readDouble;
		}
		
		
		public function gotoFirstPoint() : void {
			this._geometry.position = 0;
			this._geometry.endian = Endian.BIG_ENDIAN;
		}
		
		private function getRingsCount() : int {
			if( this._ringsCount < 1 ){
				_geometry.position = 0;
				_geometry.endian = Endian.BIG_ENDIAN;
				
				var pointsInRing:int;
				var pointSize:int = this._dimensions * (( this._storedAsFloat ) ? 4 : 8);
				this._ringsCount = 0;
				
				while( _geometry.bytesAvailable ){
					this._ringsCount++;
					pointsInRing = _geometry.readInt();
					_geometry.position += pointsInRing * pointSize;
				}
				
			}
			return this._ringsCount;
		}
		
		
		public function drawCircleOnGraphics( g:Graphics , coordsMatrix:Matrix , radius:Number ) : void {
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
				
			if( this._storedAsFloat ){
				g.drawCircle( coordsMatrix.tx + ( coordsMatrix.a * _geometry.readFloat() ) , coordsMatrix.ty + ( coordsMatrix.d * _geometry.readFloat() ) , radius );
			}
			else{
				g.drawCircle( coordsMatrix.tx + ( coordsMatrix.a * _geometry.readDouble() ) , coordsMatrix.ty + ( coordsMatrix.d * _geometry.readDouble() ) , radius  );
			}
		}
		
		public function drawCircleOnGraphicsNoTranslation( g:Graphics , coordsMatrix:Matrix , radius:Number ) : void {
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
			
			if( this._storedAsFloat ){
				g.drawCircle( ( coordsMatrix.a * _geometry.readFloat() ) , ( coordsMatrix.d * _geometry.readFloat() ) , radius );
			}
			else{
				g.drawCircle( ( coordsMatrix.a * _geometry.readDouble() ) , ( coordsMatrix.d * _geometry.readDouble() ) , radius  );
			}
		}
		
		
		//normally I use this.something but it added to the time it took so I axed it
		public function drawRingsOnGraphics( g:Graphics , coordsMatrix:Matrix ) : void {
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
			
			var x:Number;
			var y:Number;
			var pointsInRing:int;
	
			var xStart:Number = coordsMatrix.tx;
			var yStart:Number = coordsMatrix.ty;
			var xIncrement:Number = coordsMatrix.a;
			var yIncrement:Number = coordsMatrix.d;
			
			var ringsCount:int = this._ringsCount;
			
			//it is faster to have the if statements here than to use a var f:Function = (_storedAsFloat) ? readFloat : readDouble;
			//but definently not as pretty
			if( this._storedAsFloat ){
				
				while( ringsCount-- ){
					pointsInRing = _geometry.readInt();
					
					g.moveTo( xStart + ( xIncrement * _geometry.readFloat() ) , yStart + ( yIncrement * _geometry.readFloat() ) );
					pointsInRing--;
					
					while( pointsInRing-- ){
						g.lineTo( xStart + ( xIncrement * _geometry.readFloat() ) , yStart + ( yIncrement * _geometry.readFloat() ) );
					}
				}
				
			}
			else{
				
				while( ringsCount-- ){
					pointsInRing = _geometry.readInt();
					
					g.moveTo( xStart + ( xIncrement * _geometry.readDouble() ) , yStart + ( yIncrement * _geometry.readDouble() ) );
					pointsInRing--;
					
					while( pointsInRing-- ){
						g.lineTo( xStart + ( xIncrement * _geometry.readDouble() ) , yStart + ( yIncrement * _geometry.readDouble() ) );
					}
				}
				
			}
		}
		
		//normally I use this.something but it added to the time it took so I axed it
		//draw then just move the drawing to correct position
		public function drawRingsOnGraphicsNoTranslation( g:Graphics , coordsMatrix:Matrix ) : void {
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
			
			var x:Number;
			var y:Number;
			var pointsInRing:int;
			
			var xIncrement:Number = coordsMatrix.a;
			var yIncrement:Number = coordsMatrix.d;
			
			var ringsCount:int = this._ringsCount;
			
			//it is faster to have the if statements here than to use a var f:Function = (_storedAsFloat) ? readFloat : readDouble;
			//but definently not as pretty
			if( this._storedAsFloat ){
				
				while( ringsCount-- ){
					pointsInRing = _geometry.readInt();
					
					g.moveTo( xIncrement * _geometry.readFloat() , yIncrement * _geometry.readFloat() );
					pointsInRing--;
					
					while( pointsInRing-- ){
						g.lineTo( xIncrement * _geometry.readFloat() , yIncrement * _geometry.readFloat() );
					}
				}
				
			}
			else{
				
				while( ringsCount-- ){
					pointsInRing = _geometry.readInt();
					
					g.moveTo( xIncrement * _geometry.readDouble() , yIncrement * _geometry.readDouble() );
					pointsInRing--;
					
					while( pointsInRing-- ){
						g.lineTo( xIncrement * _geometry.readDouble() , yIncrement * _geometry.readDouble() );
					}
				}
				
			}
		}
		
		
		//so the standard drawRings function is as fast when zoomed out and faster when
		//zoomed in.  So I will keep this but no real need to.
		//normally I use this.something but it added to the time it took so I axed it
		public function drawGeneralizedRingsOnGraphics( g:Graphics , coordsMatrix:Matrix , generalizePixelCount:uint = 2 ) : void {
			if( generalizePixelCount == 0 ){
				return this.drawRingsOnGraphics( g , coordsMatrix );
			}
			
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
			
			var x:Number;
			var y:Number;
			var lastX:Number;
			var lastY:Number;
			var pointsInRing:int;
			
			var xStart:Number = coordsMatrix.tx;
			var yStart:Number = coordsMatrix.ty;
			var xIncrement:Number = coordsMatrix.a;
			var yIncrement:Number = coordsMatrix.d;
			
			var generalizeMapUnitsSquare:uint = generalizePixelCount/xIncrement;
			generalizeMapUnitsSquare *= generalizeMapUnitsSquare;
			
			//it is faster to have the if statements here than to use a var f:Function = (_storedAsFloat) ? readFloat : readDouble;
			//but definently not as pretty
			if( this._storedAsFloat ){
				
				while( _geometry.bytesAvailable ){
					
					pointsInRing = _geometry.readInt();
					lastX = _geometry.readFloat();
					lastY = _geometry.readFloat();
					
					g.moveTo( xStart + ( xIncrement * lastX ) , yStart + ( yIncrement * lastY ) );
					pointsInRing--;
					
					while( pointsInRing-- ){
						x = _geometry.readFloat();
						y = _geometry.readFloat();
						
						if( ((x - lastX)*(x - lastX))+((y - lastY)*(y - lastY)) > generalizeMapUnitsSquare ){
							lastX = x;
							lastY = y;
							
							g.lineTo( xStart + ( xIncrement * lastX ) , yStart + ( yIncrement * lastY ) );
						}
					}
				}
				
			}
			else{
				
				while( _geometry.bytesAvailable ){
					
					pointsInRing = _geometry.readInt();
					lastX = _geometry.readDouble();
					lastY = _geometry.readDouble();
					
					g.moveTo( xStart + ( xIncrement * lastX ) , yStart + ( yIncrement * lastY ) );
					pointsInRing--;
					
					while( pointsInRing-- ){
						x = _geometry.readDouble();
						y = _geometry.readDouble();
						
						if( ((x - lastX)*(x - lastX))+((y - lastY)*(y - lastY)) > generalizeMapUnitsSquare ){
							lastX = x;
							lastY = y;
							
							g.lineTo( xStart + ( xIncrement * lastX ) , yStart + ( yIncrement * lastY ) );
						}
					}
				}
			}
		}
		
		
		//so the standard drawRings function is as fast when zoomed out and faster when
		//zoomed in.  So I will keep this but no real need to.
		//normally I use this.something but it added to the time it took so I axed it
		public function generalizeRings( coordsMatrix:Matrix , generalizePixelCount:uint = 2 , isPolygon:Boolean = true ) : void {
			if( generalizePixelCount == 0 ){
				return;
			}
			
			var i:int = 0;
			var j:int = 0;
			var numRings:uint = 0;
			var numPts:uint = 0;
			var pts:Array;
			var ring:Array;
			var rings:Array = new Array();
			var newGeometry:ByteArray = new ByteArray();
			newGeometry.position = 0;
			newGeometry.endian = Endian.BIG_ENDIAN;
			
			_geometry.position = 0;
			_geometry.endian = Endian.BIG_ENDIAN;
			
			var x:Number;
			var y:Number;
			var lastX:Number;
			var lastY:Number;
			var pointsInRing:int;
			
			var xStart:Number = coordsMatrix.tx;
			var yStart:Number = coordsMatrix.ty;
			var xIncrement:Number = coordsMatrix.a;
			var yIncrement:Number = coordsMatrix.d;
			
			var generalizeMapUnitsSquare:uint = generalizePixelCount/xIncrement;
			generalizeMapUnitsSquare *= generalizeMapUnitsSquare;
			
			
			//it is faster to have the if statements here than to use a var f:Function = (_storedAsFloat) ? readFloat : readDouble;
			//but definently not as pretty
			if( this._storedAsFloat ){
				
				while( _geometry.bytesAvailable ){
					
					pointsInRing = _geometry.readInt();
					lastX = _geometry.readFloat();
					lastY = _geometry.readFloat();
					pointsInRing--;
					
					ring = new Array( lastX , lastY );
					
					while( pointsInRing-- ){
						x = _geometry.readFloat();
						y = _geometry.readFloat();
						
						if( ((x - lastX)*(x - lastX))+((y - lastY)*(y - lastY)) > generalizeMapUnitsSquare ){
							lastX = x;
							lastY = y;
							ring.push( lastX , lastY );
						}
					}
					if( ring.length >= 6 || isPolygon == false ){
						rings.push( ring );
					}
				}
				
				numRings = rings.length;
				for( i = 0 ; i < numRings; i++ ){
					pts = rings[i] as Array;
					numPts = pts.length;
					newGeometry.writeInt( numPts >> 1 );
					for( j = 0; j < numPts; j++ ){
						newGeometry.writeFloat( pts[j] );	
					}
				}
			}
			else{
				
				while( _geometry.bytesAvailable ){
					
					pointsInRing = _geometry.readInt();
					lastX = _geometry.readDouble();
					lastY = _geometry.readDouble();
					pointsInRing--;
					
					ring = new Array( lastX , lastY );
					
					while( pointsInRing-- ){
						x = _geometry.readDouble();
						y = _geometry.readDouble();
						
						if( ((x - lastX)*(x - lastX))+((y - lastY)*(y - lastY)) > generalizeMapUnitsSquare ){
							lastX = x;
							lastY = y;
							ring.push( lastX , lastY );
						}
					}
					if( ring.length >= 6 || isPolygon == false ){
						rings.push( ring );
					}
				}
				
				numRings = rings.length;
				for( i = 0; i < numRings; i++ ){
					pts = rings[i] as Array;
					numPts = pts.length;
					newGeometry.writeInt( numPts >> 1 );
					
					for( j = 0; j < numPts; j++ ){
						newGeometry.writeDouble( pts[j] );	
					}
				}
			}
			
			this._ringsCount = numRings;
			this._geometry = newGeometry;
		}
		
	}
}